//
// Created by Gorun on 2022/5/3.
//

#ifndef LOXPP_ENVIRONMENT_H
#define LOXPP_ENVIRONMENT_H

#include <any>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <utility>

#include "Error.h"
#include "Token.h"

using namespace std;

class Environment: public enable_shared_from_this<Environment> {
private:
    map<string, any> values;

public:
    shared_ptr<Environment> enclosing;
    Environment() {
        enclosing = nullptr;
    }

    explicit Environment(shared_ptr<Environment> enclosing) {
        this->enclosing = std::move(enclosing);
    }

    any get(const Token& name) {
        auto element = values.find(name.lexeme);
        if (element!=values.end()) { //Then there's a variable named by, well, name
            return element->second;
        }

        if (enclosing!=nullptr)
            return enclosing->get(name);

        throw RuntimeError(name, "Undefined variable '"+name.lexeme+"'.");
    }

    any getAt(int distance, const string& name) {
        return ancestor(distance)->values[name];
    }

    void assign(const Token& name, any value) {
        auto element = values.find(name.lexeme);
        if (element!=values.end()) {
            element->second = move(value);
            return;
        }

        if (enclosing!=nullptr) {
            enclosing->assign(name, value);
            return;
        }

        throw RuntimeError(name, "Undefined variable '"+name.lexeme+"'.");
    }

    void assignAt(int distance, const Token& name, any value) {
        ancestor(distance)->values[name.lexeme]=move(value);
    }

    //!Attention! This method moves value away!
    void define(const string& name, any value) {
        values[name]=move(value);
    }

    shared_ptr<Environment> ancestor(int distance) {
        shared_ptr<Environment> environment = shared_from_this();
        for (int i=0; i<distance; i++) {
            environment=environment->enclosing;
        }
        return environment;
    }
};

#endif //LOXPP_ENVIRONMENT_H
